package fr.ysa.persistence;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateQueryException;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;

import fr.ysa.persistence.requetes.ConfigRequetes;
import fr.ysa.persistence.requetes.Trie;
import fr.ysa.persistence.requetes.filtres.Filtre;
import fr.ysa.persistence.requetes.filtres.utils.FiltreUtil;

@Repository("hibernateDao")
public abstract class ImplHibernateDao<T extends Entity> extends
		HibernateDaoSupport implements IDao<T> {

	public ImplHibernateDao(SessionFactory sessionFactory) {
		super.setSessionFactory(sessionFactory);
	}

	protected abstract Class<T> getReferenceClass();

	@Autowired
	FiltreUtil filtreUtil;

	@Override
	public final Collection<T> sauvegarder(Collection<T> entities) throws DaoException {
		try {
			this.getHibernateTemplateDAO().saveOrUpdateAll(entities);
			return entities;
		} catch (Exception he) {
			throw new DaoException(he);
		}
	}

	@Override
	public T sauvegarder(T obj) throws DaoException {
		try {
			this.getHibernateTemplateDAO().save(obj);
			return obj;
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	public HibernateTemplate getHibernateTemplateDAO() {
		return this.getHibernateTemplate();
	}

	@Override
	public final T modifier(final T obj) throws DaoException {
		try {
			this.getHibernateTemplateDAO().update(obj);
			return obj;
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@Override
	public final void supprimer(final T obj) throws DaoException {
		try {
			this.getHibernateTemplateDAO().delete(obj);
			this.getHibernateTemplateDAO().flush();
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@Override
	public final void supprimerParId(final Serializable id) throws DaoException {
		try {
			this.supprimer(this.trouver(id));
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@Override
	public void supprimer(Collection<T> entities) throws DaoException {
		try {
			this.getHibernateTemplateDAO().deleteAll(entities);
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer compter(ConfigRequetes config) throws DaoException {
		try {
			try {
				Integer result = Integer.valueOf(0);
				DetachedCriteria crit = DetachedCriteria
						.forClass(getReferenceClass());
				
				addTrie(crit, config);

				addFiltre(crit, config);

				addGroup(crit, config);
								
				crit.setProjection(Projections.rowCount());
				List<T> results = this.getHibernateTemplateDAO()
						.findByCriteria(crit);
				if (results.size() != 0) {
					// result = (Integer) results.get(0);
				}
				return result.intValue();
			} catch (HibernateQueryException hqe) {
				throw new DaoException(hqe);
			} catch (HibernateException he) {
				throw new DaoException(he);
			}
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@Override
	public T trouver(Serializable key) throws DaoException {
		try {
			return this.getReferenceClass().cast(
					this.getHibernateTemplateDAO()
							.get(getReferenceClass(), key));
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> trouver(ConfigRequetes config) throws DaoException {
		try {
			DetachedCriteria crit = DetachedCriteria
					.forClass(getReferenceClass());

			addTrie(crit, config);

			addFiltre(crit, config);

			addGroup(crit, config);

			crit.getExecutableCriteria(getSession()).setFirstResult(
					config.getDebut());

			if (config.getNombre() > 0) {
				crit.getExecutableCriteria(getSession()).setMaxResults(
						config.getNombre());
			}

			if (config.getRegroupement().size() > 0) {
				crit.setResultTransformer(Transformers
						.aliasToBean(getReferenceClass()));
			}

			List<T> liste = this.getHibernateTemplateDAO().findByCriteria(crit);
			return liste;
		} catch (HibernateException he) {
			throw new DaoException(he);
		}
	}

	private void addFiltre(DetachedCriteria crit, ConfigRequetes config) {
		if (config.getFiltres() != null) {
			for (Filtre filtre : config.getFiltres()) {
				List<SimpleExpression> expressions = filtreUtil
						.getRestrictionsExpression(filtre);
				for (SimpleExpression expression : expressions) {
					crit.add(expression);
				}
			}
		}
	}
	
	private void addTrie(DetachedCriteria crit, ConfigRequetes config) {
		for (Trie trie : config.getTries()) {
			switch (trie.getOrdre()) {
			case ASC:
				crit.addOrder(Order.asc(trie.getChamp()));
				break;

			case DESC:
				crit.addOrder(Order.desc(trie.getChamp()));
				break;

			default:
				crit.addOrder(Order.asc(trie.getChamp()));
				break;
			}
		}
	}
	
	private void addGroup(DetachedCriteria crit, ConfigRequetes config) {
		for (String group : config.getRegroupement()) {
			crit.setProjection(Projections.groupProperty(group));
		}
	}
	
}
